/*
 * hilosFunciones.c
 *
 *  Created on: 24/11/2011
 *      Author: utn_so
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>
#include "Protocolo_NIPC.h"
#include "funcionesYEstructurasRaid.h"
#include "hilosFunciones.h"
#include <time.h>

extern int32_t offsetSectorMaximo;
extern tipoConfigPRAID configPRAID;
extern int32_t discosSincronizando;
extern t_queue *auxConPedidos;
extern nodoDiscos *discosActivos;
extern nodoDiscos *siguienteDiscoAMandarPedido;
extern sem_t semaforoSincro;

void copiarDatosFS(paquete *NIPC, pedido *ped, uint32_t socket) {
	if(NIPC->type==escritura){
		ped->sector.info = (char*)malloc(512);
		memcpy(ped->sector.info, NIPC->payload, 512);
	}
	else
		ped->sector.info = NULL;

	ped->sector.sect = NIPC->nroSector;
	ped->socket = socket;
	ped->operacion = NIPC->type;
	ped->destino = fileSystem;
	ped->discoOrigen = NULL;
}

void copiarDatosDisco(paquete *NIPC, pedido ped) {
	if(ped.operacion==escritura)
		memcpy(NIPC->payload, ped.sector.info, 512);
	//Si es de lectura manda basura
	NIPC->nroSector = ped.sector.sect;
	NIPC->payload_Length = sizeof(paquete);
	NIPC->type = ped.operacion;
}

/*int32_t hayQCambiarDeDisco(int32_t sector, int32_t sectoresPorDisco, nodoDiscos **losDiscos){
	int32_t idDisco;
	if(sector==0)
		idDisco = primerDiscoSincronizado(*losDiscos);
	if(sector>sectoresPorDisco)
		idDisco = siguienteDiscoSincronizado(idDisco, );
	return idDisco;
}*/

void mandarPedidosLectura(char **argumentos) {
	nodoDiscos *discoPedido= (nodoDiscos*)argumentos[0];
	uint32_t idDisco = *argumentos[2];
	nodoDiscos *discoASincronizar = (nodoDiscos*)argumentos[1];
	int32_t sector;
	pedido pedidoLectura;
	for (sector = 0; sector < offsetSectorMaximo; sector++) {
		//sem_wait(&semaforoSincro);
		//TODO Sacar afuera las asignaciones innecesarias
		pedidoLectura.socket = idDisco;
		pedidoLectura.operacion = lectura;
		pedidoLectura.sector.info = NULL;
		pedidoLectura.sector.sect = sector;
		pedidoLectura.destino = disco;
		pedidoLectura.discoOrigen = discoASincronizar;
		pedido *pedidoALeer = malloc(sizeof(pedido));
		memcpy(pedidoALeer, &pedidoLectura, sizeof(pedido));
		collection_queue_push(discoPedido->pedidos, pedidoALeer);
		sem_post(&discoPedido->pedidos->semaforoPedidos);
		if(pedidoLectura.sector.sect % 10000 == 0)
			printf("Va mandando a leer por el sector %d\n", pedidoLectura.sector.sect);
		//usleep(250); //Para que lea y escriba mas o menos simultaneamente
	}
}

void sincronizarDisco(nodoDiscos **losDiscos, uint32_t idDisco, char* nombreDisco) {
	discosSincronizando++;
	nodoDiscos *discoASincronizar;
	discoASincronizar = *losDiscos;

	time_t tiempo = time(0);
	struct tm *tlocal = localtime(&tiempo);
	char* horaInicio = malloc(20);
	char* horaFin = malloc(20);
	strftime(horaInicio, 20, "20%y.%m.%d %H:%M:%S", tlocal);

	nodoDiscos discoPedido;
	discoPedido = discoconMenosPedidos(*losDiscos);

	/*Crea hilo para mandar los pedidos a leer*/
	pthread_t idHiloPed;
	//pthread_attr_t atributoHiloPed;
	//pthread_attr_init(&atributoHiloPed);
	//pthread_attr_setdetachstate(&atributoHiloPed, PTHREAD_CREATE_JOINABLE);
	char *argumentos[3];
	argumentos[0]=(char*)&discoPedido;
	argumentos[1]=(char*)discoASincronizar;
	argumentos[2]=(char*)&idDisco;
	pthread_create(&idHiloPed, NULL, (void*) mandarPedidosLectura, argumentos);

	/*Pido todos los sectores a los Discos que esten sincronizados*/

	pedido *pedidoEscritura;
	paquete *NIPC;
	NIPC = (paquete*) malloc(sizeof(paquete));
	paquete *paqueteRespuesta;
	paqueteRespuesta = (paquete*) malloc(sizeof(paquete));

	/*Manda a escribir los sectores al PPD*/
	int32_t sectorAEscribir;
	sectorAEscribir = offsetSectorMaximo;
	while (sectorAEscribir > 0) {
		//if (discoASincronizar->pedidos->elements_count != 0) {
			sem_wait(&(discoASincronizar->pedidos->semaforoPedidos));
			pedidoEscritura = (pedido*)collection_queue_pop(discoASincronizar->pedidos);
			copiarDatosDisco(NIPC, *pedidoEscritura);
			if(pedidoEscritura->sector.sect % 10000 == 0)
				printf("Va escribiendo por el sector %d\n", pedidoEscritura->sector.sect);
			if(RAID_MANDAPEDIDO(idDisco, NIPC)==-1)
				perror("Error en el send");
			free(pedidoEscritura->sector.info);
			free(pedidoEscritura);
			RAID_RECIBEPEDIDO(idDisco, paqueteRespuesta);
			sectorAEscribir--;
			//sem_post(&semaforoSincro);
		//}
	}
	free(paqueteRespuesta);
	//pthread_join(idHiloPed, NULL);
	discoASincronizar->estadoDisco = sincronizado;
	tiempo = time(0);
	tlocal = localtime(&tiempo);
	strftime(horaFin, 20, "20%y.%m.%d %H:%M:%S", tlocal);
	discosSincronizando--;
	if(discosSincronizando==0)
		/*hilo para distribuir pedidos de escritura encolados por estar sincronizando*/
		crearHiloParaPedidosEscritura(losDiscos);
	if (configPRAID.consolaActivada)
		mensajes(nombreDisco, sincronizaDisco, horaInicio, horaFin);
}

int32_t atenderPedido(nodoDiscos **losDiscos, uint32_t idDisco, pedido ped) {
	paquete *paq;
	paq = (paquete*) malloc(sizeof(paquete));
	//if (ped.operacion == escritura) {
		//todo sincronizar entre los hilos de los discos
	copiarDatosDisco(paq, ped);
	if (RAID_MANDAPEDIDO(idDisco, paq) == -1)
		return -1;
	/*} else {
		copiarDatosDisco(paq, ped);
		RAID_MANDAPEDIDO(idDisco, paq);
	}*/
	free(paq);
	return 0;
}

void respuestaDisco(nodoDiscos *losDiscos) {
	uint32_t idDisco;
	nodoDiscos *discoHilo;
	discoHilo = (nodoDiscos*) malloc(sizeof(nodoDiscos));
	discoHilo = losDiscos;
	idDisco = losDiscos->idDisco;
	if (discoHilo->estadoDisco == noSincronizado) {
		printf("Iniciando sincronizacion\n");
		sincronizarDisco(&losDiscos, idDisco, losDiscos->nombreDisco);
	}
	while (1) {
		sem_wait(&(discoHilo->pedidos->semaforoPedidos));
		//if (discoHilo->pedidos->elements_count != 0) {
			int32_t resultado=0;
			pedido *ped;
			ped = (pedido*) collection_queue_pop(discoHilo->pedidos);
			paquete *paqRespuesta;
			if (atenderPedido(&losDiscos, idDisco, *ped) != -1) {
				paqRespuesta = (paquete*) malloc(sizeof(paquete));
				resultado = RAID_RECIBEPEDIDO(idDisco, paqRespuesta);
			} else
				resultado = -1;
			if (resultado == -1) {
				printf("Se desconecto el disco %s\n", discoHilo->nombreDisco);
				nodoDiscos disco;
				discoHilo->estadoDisco = noSincronizado;
				SacarDisco(&discosActivos, idDisco);
				disco = discoconMenosPedidos(discosActivos);
				//ped->socket = disco.idDisco;
				collection_queue_push(disco.pedidos, ped);
				sem_post(&disco.pedidos->semaforoPedidos);
				while (discoHilo->pedidos->elements_count != 0) {
					sem_wait(&(discoHilo->pedidos->semaforoPedidos));
					ped = (pedido*)collection_queue_pop(discoHilo->pedidos);
					//ped->socket = siguienteDiscoAMandarPedido->idDisco;
					disco = discoconMenosPedidos(discosActivos);
					collection_queue_push(disco.pedidos, ped);
					sem_post(&disco.pedidos->semaforoPedidos);
					/*if (siguienteDiscoAMandarPedido->sig == NULL)
						siguienteDiscoAMandarPedido = discosActivos;
					else
						siguienteDiscoAMandarPedido = siguienteDiscoAMandarPedido->sig;*/
				}
				close(idDisco);
				//free(discoHilo);
				//pthread_exit(NULL);
			}
			else {
			if (ped->destino == disco) {
				ped->sector.info = (char*) malloc(512);
				memcpy(ped->sector.info, paqRespuesta->payload, 512);
				ped->operacion = escritura;
				collection_queue_push(ped->discoOrigen->pedidos, ped);
				sem_post(&ped->discoOrigen->pedidos->semaforoPedidos);
			} else {
				RAID_MANDAPEDIDO(ped->socket, paqRespuesta);
				if (configPRAID.consolaActivada) {
					if (ped->operacion == lectura)
						mensajes(losDiscos->nombreDisco, seHizoRead, NULL, NULL);
					else
						mensajes(losDiscos->nombreDisco, seHizoWrite, NULL,
								NULL);
				}
				if (ped->operacion == escritura)
					free(ped->sector.info);
				free(ped);
			}
			free(paqRespuesta);
		}
		}
	//}
}

void crearHiloDisco(nodoDiscos **losDiscos) {
	pthread_t idHilo;
	pthread_attr_t atributoHilo;
	pthread_attr_init(&atributoHilo);
	pthread_attr_setdetachstate (&atributoHilo, PTHREAD_CREATE_JOINABLE);
	pthread_create(&idHilo, &atributoHilo, (void*)respuestaDisco, *losDiscos);
}

void redistribucionPedidosescritura(nodoDiscos **losDiscos){
	pedido *pedidoParaEscribir;
	while(auxConPedidos!=NULL){
		pedidoParaEscribir=(pedido*)collection_queue_pop(auxConPedidos);
		//pedidoParaEscribir = sacarSector(&auxConPedidos);
		ponerPedidoEnDiscos(pedidoParaEscribir, losDiscos);
	}
}

void crearHiloParaPedidosEscritura(nodoDiscos **losDiscos){
	pthread_t idHilo;
	pthread_attr_t atributoHilo;
	pthread_attr_init(&atributoHilo);
	//pthread_attr_setdetachstate(&atributoHilo, PTHREAD_CREATE_JOINABLE);
	pthread_create(&idHilo, &atributoHilo, (void*)redistribucionPedidosescritura, *losDiscos);
}
